package com.yyfax.pay.depository.service.inner.impl;

import java.util.Date;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import com.yyfax.commons.lang.DateUtil;
import com.yyfax.framework.common.Log;
import com.yyfax.framework.common.OpResponse;
import com.yyfax.framework.service.CacheService;
import com.yyfax.framework.yyfax.CryptUtil;
import com.yyfax.framework.yyfax.enums.IdCardTypeEnum;
import com.yyfax.pay.common.constants.CacheName;
import com.yyfax.pay.common.enums.FuiouBusiTypeEnum;
import com.yyfax.pay.common.enums.FuiouDepositStateEnum;
import com.yyfax.pay.common.exception.PayException;
import com.yyfax.pay.common.exception.PayExceptionCode;
import com.yyfax.pay.common.model.vo.PayRspCodeVO;
import com.yyfax.pay.common.util.FuiouIdUtils;
import com.yyfax.pay.depository.dao.FuiouUserProtocolDao;
import com.yyfax.pay.depository.domain.FuiouUserProtocol;
import com.yyfax.pay.depository.service.inner.FuiouUserService;
import com.yyfax.pay.depository.vo.FuiouUserProtocolVO;
import com.yyfax.pay.third.facade.fuioudeposit.FuiouDepositService;
import com.yyfax.pay.third.model.to.fuioudeposit.xml.ProtocalSignSmsPlain;
import com.yyfax.pay.vo.depository.QueryUserProtocolSignReq;
import com.yyfax.pay.vo.depository.QueryUserProtocolSignRsp;
import com.yyfax.pay.vo.depository.UserProtocolSignSmsReq;
import com.yyfax.pay.vo.depository.UserProtocolSignSmsRsp;
import com.yyfax.pay.vo.depository.UserProtocolSigningReq;

@Service
public class FuiouUserServiceImpl implements FuiouUserService {
	private final Logger logger = LoggerFactory.getLogger(getClass());

	/**
	 * 协议绑定获取短信时间间隔，默认120秒
	 */
	@Value(value = "${pay.config.fuiou_deposit.protocolSignSmsCoolDownTime:120}")
	private int protocolSignSmsCoolDownTime;

	/**
	 * 协议绑定最大获取短信次数，默认3次
	 */
	@Value(value = "${pay.config.fuiou_deposit.protocolSignSmsMaxTimes:3}")
	private int protocolSignSmsMaxTimes;

	/**
	 * 协议绑定短信令牌失效时间，30分钟 = 30*60=1800秒
	 */
	@Value(value = "${pay.config.fuiou_deposit.protocolSignSmsTokenExpire:1800}")
	private int protocolSignSmsTokenExpire;

	/**
	 * 协议发送短信，缓存失效单位，时间单位：时
	 */
	private final int PROTOCOL_SIGN_SMS_EXPIRE = 24;

	@Resource
	private FuiouUserProtocolDao fuiouUserProtocolDao;

	@Resource
	private FuiouDepositService fuiouDepositService;

	@Resource(name = "payCacheService")
	private CacheService cacheService;

	@Resource
	private FuiouIdUtils fuiouIdUtils;

	@Override
	public FuiouUserProtocolVO saveProtocol(String merchantCode, UserProtocolSignSmsReq params) {
		final String op = "FuiouUserServiceImpl.saveProtocol";
		FuiouUserProtocol fuiouUserProtocol = new FuiouUserProtocol();

		String idNoCip = null;
		String cardNoCip = null;
		try {
			idNoCip = CryptUtil.encrypt(params.getIdNo());
			cardNoCip = CryptUtil.encrypt(params.getCardNo());
		} catch (Exception e1) {
			logger.error(Log.op(op).msg("存管绑定协议，证件号/银行卡号加密异常").kv("merchantCode", merchantCode).kv("orderId", params.getOrderId())
					.kv("idNo", params.getIdNo()).kv("cardNo", params.getCardNo()).toString());
			throw PayExceptionCode.SYS_ERROR.exp();
		}

		try {
			// String busiId = merchantCode.concat(params.getOrderId());
			String busiId = fuiouIdUtils.generateBusiId(FuiouBusiTypeEnum.PROTOCOL_SIGN, merchantCode);
			merchantCode.concat(params.getOrderId());
			fuiouUserProtocol.setBusiId(busiId);
			fuiouUserProtocol.setOrderId(params.getOrderId());
			fuiouUserProtocol.setOrderTime(DateUtil.str2DateTime(params.getOrderTime()));
			fuiouUserProtocol.setMerchantCode(merchantCode);
			fuiouUserProtocol.setLoginId(params.getLoginId());
			fuiouUserProtocol.setTerminal(params.getTerminal());
			fuiouUserProtocol.setUserId(params.getUserId());
			fuiouUserProtocol.setName(params.getName());
			fuiouUserProtocol.setIdType(IdCardTypeEnum.PRC_ID.getValue());
			fuiouUserProtocol.setIdNoCip(idNoCip);
			fuiouUserProtocol.setPhone(params.getPhone());
			fuiouUserProtocol.setCardNoCip(cardNoCip);
			fuiouUserProtocol.setIndateNum(params.getIndateNum());
			fuiouUserProtocol.setState(FuiouDepositStateEnum.INI.getValue());

			fuiouUserProtocolDao.save(fuiouUserProtocol);
		} catch (DuplicateKeyException e) {
			logger.warn(Log.op("FuiouUserServiceImpl.saveProtocol").msg("存管绑定协议，保存订单主键冲突").kv("merchantCode", merchantCode).kv("params", params)
					.toString(), e);

			fuiouUserProtocol = fuiouUserProtocolDao.get(merchantCode, params.getOrderId());
		}

		return FuiouUserProtocolVO.newInstance().fuiouUserProtocol(fuiouUserProtocol);
	}

	@Override
	public void doProtocolSignSmsCooldown(FuiouUserProtocol fuiouUserProtocol) {
		// 一个订单最多获取三次短信，且每次时间间隔120秒

		// 获取短信间隔
		boolean add = cacheService.add(getProtocolSignSmsCooldownKey(fuiouUserProtocol), "protocolSignSmsCooldown", protocolSignSmsCoolDownTime);
		if (!add) {
			logger.warn(Log.op("FuiouUserServiceImpl.doProtocolSignSmsCooldown").msg("获取短信操作频繁，2分钟以内只能获取一次短信验证码")
					.kv("busiId", fuiouUserProtocol.getBusiId()).kv("userId", fuiouUserProtocol.getUserId()).kv("phone", fuiouUserProtocol.getPhone())
					.toString());
			throw PayExceptionCode.ERR0R_DUPLICATE.exp();
		}

		// 获取短信次数限制
		int incrAndGet = cacheService.incrAndGet(getProtocolSignSmsCooldownTimesKey(fuiouUserProtocol),
				DateUtil.addHour(new Date(), PROTOCOL_SIGN_SMS_EXPIRE));
		if (incrAndGet > protocolSignSmsMaxTimes) {
			logger.warn(Log.op("FuiouUserServiceImpl.doProtocolSignSmsCooldown").msg("获取短信次数超过限制" + protocolSignSmsMaxTimes)
					.kv("busiId", fuiouUserProtocol.getBusiId()).kv("userId", fuiouUserProtocol.getUserId()).kv("phone", fuiouUserProtocol.getPhone())
					.toString());
			throw PayExceptionCode.VERIFY_CODE_OVER_LIMIT.exp();
		}
	}

	/**
	 * 获取协议绑定发送短信间隔缓存key<br>
	 * 
	 * @author buyi
	 * @date 2018-06-19 14:50:55
	 * @since v2.2.2
	 * @param userId
	 * @return
	 */
	private String getProtocolSignSmsCooldownKey(FuiouUserProtocol fuiouUserProtocol) {
		return CacheName.FUIOU_DEPOSIT_PROTOCOL_SIGN_SMS_COOLDOWN_TIME_KEY.concat("_").concat(fuiouUserProtocol.getBusiId());
	}

	/**
	 * 获取协议绑定发送短信次数key<br>
	 * 次数限制已的订单为维度
	 * 
	 * @author buyi
	 * @date 2018-06-19 14:51:24
	 * @since v2.2.2
	 * @param userId
	 * @return
	 */
	private String getProtocolSignSmsCooldownTimesKey(FuiouUserProtocol fuiouUserProtocol) {
		return CacheName.FUIOU_DEPOSIT_PROTOCOL_SIGN_SMS_COOLDOWN_TIME_KEY.concat("_times_").concat(fuiouUserProtocol.getBusiId());
	}

	@Override
	public OpResponse doProtocolSignSmsComposite(FuiouUserProtocol fuiouUserProtocol) {
		try {

			if (fuiouUserProtocol == null) {
				logger.error(Log.op("FuiouUserServiceImpl.doProtocolSignSmsComposite").msg("富友存管协议绑定获取短信失败，请求订单信息为空").toString());
				return OpResponse.fail(PayExceptionCode.ORDER_NOT_FOUND.getCode(), PayExceptionCode.ORDER_NOT_FOUND.getDesc(), null);
			}

			UserProtocolSignSmsRsp content = doProtocolSignSms(fuiouUserProtocol);

			OpResponse opResponse = OpResponse.success();
			opResponse.setContent(content);

			return opResponse;
		} catch (PayException e) {
			return OpResponse.fail(e.getCode(), e.getMessage(), null);
		}
	}

	/**
	 * 发送短信
	 * 
	 * @author buyi
	 * @date 2018-06-14 17:33:47
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @return
	 */
	private UserProtocolSignSmsRsp doProtocolSignSms(FuiouUserProtocol fuiouUserProtocol) {
		// 请求富友存管绑定协议短信接口
		PayRspCodeVO rspCodeVO = fuiouDepositService.doProtocolSignSms(fuiouUserProtocol);

		if (PayExceptionCode.SUCCESS.isCode(rspCodeVO.getCode())) {
			// 如果发送短信验证码请求成功，则封装响应参数
			ProtocalSignSmsPlain plain = (ProtocalSignSmsPlain) rspCodeVO.getObj();

			// 更新txnDate，第三方以“商户号+订单号+时间”确定一笔交易，防止跨天的情况，持久化
			fuiouUserProtocolDao.updateTxnDate(fuiouUserProtocol.getBusiId(), plain.getTxnDate());

			// 设置获取短信的令牌到缓存,用于协议确认接口的请求,使用第三方的salt作为token
			setProtocolSignSmsToken(fuiouUserProtocol, plain.getSalt());

			return UserProtocolSignSmsRsp.newInstance().token(plain.getSalt());
		}

		// 如果发送短信不成功，则抛出业务异常
		throw new PayException(rspCodeVO.getCode(), rspCodeVO.getDesc());
	}

	/**
	 * 设置协议绑定令牌到缓存
	 * 
	 * @author buyi
	 * @date 2018-06-19 19:53:35
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @param token
	 */
	private void setProtocolSignSmsToken(FuiouUserProtocol fuiouUserProtocol, String token) {
		String key = getProtocolSignSmsTokenKey(fuiouUserProtocol);
		cacheService.set(key, token, protocolSignSmsTokenExpire);
	}

	/**
	 * 获取协议绑定获取短信令牌key
	 * 
	 * @author buyi
	 * @date 2018-06-19 19:50:15
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @return
	 */
	private String getProtocolSignSmsTokenKey(FuiouUserProtocol fuiouUserProtocol) {
		return CacheName.FUIOU_DEPOSIT_PROTOCOL_SIGN_SMS_TOKEN_KEY.concat("_").concat(fuiouUserProtocol.getBusiId());
	}

	@Override
	public OpResponse doProtocolSigningComposite(String merchantCode, UserProtocolSigningReq params) {
		final String op = "FuiouUserServiceImpl.doProtocolSigningComposite";
		try {
			FuiouUserProtocol fuiouUserProtocol = fuiouUserProtocolDao.get(merchantCode, params.getOrderId());
			if (fuiouUserProtocol == null) {
				logger.error(Log.op(op).msg("富友存管协议绑定确认,请求订单信息为空").kv("merchantCode", merchantCode).kv("orderId", params.getOrderId()).toString());
				return OpResponse.fail(PayExceptionCode.ORDER_NOT_FOUND.getCode(), PayExceptionCode.ORDER_NOT_FOUND.getDesc(), null);
			}

			// 校验协议确认请求
			validProtocolSigning(fuiouUserProtocol, params.getToken());

			if (StringUtils.isBlank(fuiouUserProtocol.getTxnDate())) {
				// 如果交易时间为空，则返回失败。参数txnDate为请求短信接口富友存管响应参数
				logger.error(Log.op(op).msg("富友存管协议绑定确认,请求订单信息为空").kv("busiId", fuiouUserProtocol.getBusiId()).kv("merchantCode", merchantCode)
						.kv("orderId", params.getOrderId()).toString());
				return OpResponse.fail(PayExceptionCode.ERROR_ILL_PARAMETER.getCode(), PayExceptionCode.ERROR_ILL_PARAMETER.getDesc(), null);
			}

			if (!FuiouDepositStateEnum.INI.isValue(fuiouUserProtocol.getState())) {
				// 如果订单状态非初始化状态，则表示订单存在交易结果，或者中间态，直接返回交易结果
				logger.info(Log.op(op).msg("富友存管协议绑定确认,订单已经存在结果，直接返回").kv("busiId", fuiouUserProtocol.getBusiId())
						.kv("state", fuiouUserProtocol.getState()).toString());
				return OpResponse.fail(fuiouUserProtocol.getRspCode(), fuiouUserProtocol.getRspMsg(), null);
			}

			PayRspCodeVO payRspCodeVO = doProtocolSigning(fuiouUserProtocol, params.getToken(), params.getValidCode());

			OpResponse opResponse = new OpResponse();
			opResponse.setCode(payRspCodeVO.getCode());
			opResponse.setDisplay(payRspCodeVO.getDesc());
			return opResponse;
		} catch (PayException e) {
			return OpResponse.fail(e.getCode(), e.getMessage(), null);
		}
	}

	/**
	 * 校验协议确认请求<br>
	 * 包含：令牌校验，和验证码输错校验
	 * 
	 * @author buyi
	 * @date 2018-06-26 17:20:35
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @param token
	 */
	private void validProtocolSigning(FuiouUserProtocol fuiouUserProtocol, String token) {
		// 判断token有效性
		validProtocolSignSmsToken(fuiouUserProtocol, token);

		// 短信验证码输错次数控制
		validProtocolSigningMaxError(fuiouUserProtocol);

	}

	/**
	 * 校验协议确认令牌有效性
	 * 
	 * @author buyi
	 * @date 2018-06-26 17:23:04
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @param token
	 */
	private void validProtocolSignSmsToken(FuiouUserProtocol fuiouUserProtocol, String token) {
		String key = getProtocolSignSmsTokenKey(fuiouUserProtocol);
		String expectToken = cacheService.get(key);

		if (StringUtils.isBlank(expectToken) || !expectToken.equals(token)) {
			// 如果相应的订单号不存在token，或者令牌不一致，则抛出验证码失效异常
			logger.warn(Log.op("FuiouUserServiceImpl.validProtocolSignSmsToken").msg("富友存管协议绑定，token失效")
					.kv("merchantCode", fuiouUserProtocol.getMerchantCode()).kv("busiId", fuiouUserProtocol.getBusiId())
					.kv("expectToken", expectToken).kv("actToken", token).toString());
			throw PayExceptionCode.VERIFY_CODE_INVALID.exp();
		}
	}

	/**
	 * 校验协议确认验证码输错次数
	 * 
	 * @author buyi
	 * @date 2018-06-26 17:24:01
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 */
	public void validProtocolSigningMaxError(FuiouUserProtocol fuiouUserProtocol) {
		// 获取短信次数限制
		int incrAndGet = cacheService.incrAndGet(getProtocolSigningMaxErrorTimesKey(fuiouUserProtocol),
				DateUtil.addSecond(new Date(), protocolSignSmsTokenExpire));
		if (incrAndGet > protocolSignSmsMaxTimes) {
			logger.warn(Log.op("FuiouUserServiceImpl.doProtocolSignSmsCooldown").msg("获取短信次数超过限制" + protocolSignSmsMaxTimes)
					.kv("busiId", fuiouUserProtocol.getBusiId()).kv("userId", fuiouUserProtocol.getUserId()).kv("phone", fuiouUserProtocol.getPhone())
					.toString());

			throw PayExceptionCode.VERIFY_CODE_MAX_ERROR.exp();
		}
	}

	/**
	 * 协议确认次数key
	 * 
	 * @author buyi
	 * @date 2018-06-26 17:08:13
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @return
	 */
	private String getProtocolSigningMaxErrorTimesKey(FuiouUserProtocol fuiouUserProtocol) {
		return CacheName.FUIOU_DEPOSIT_PROTOCOL_SIGN_SMS_COOLDOWN_TIME_KEY.concat("_signing_max_times_").concat(fuiouUserProtocol.getBusiId());
	}

	/**
	 * 协议绑定确认
	 * 
	 * @author buyi
	 * @date 2018-06-19 20:04:21
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 * @param salt 富友返回的盐
	 * @param validCode 短信验证码
	 * @return
	 */
	private PayRspCodeVO doProtocolSigning(FuiouUserProtocol fuiouUserProtocol, String salt, String validCode) {
		final String op = "FuiouUserServiceImpl.doProtocolSigning";

		// 更新订单为处理中
		int i = fuiouUserProtocolDao.updateState(fuiouUserProtocol.getBusiId(), FuiouDepositStateEnum.DEALING.getValue(),
				fuiouUserProtocol.getState(), PayExceptionCode.DEALING.getCode(), PayExceptionCode.DEALING.getDesc());
		if (i <= 0) {
			// 如果更新状态没有命中，则表示订单已经被变更，则判断交易结果
			FuiouUserProtocol po = fuiouUserProtocolDao.getByBusiId(fuiouUserProtocol.getBusiId());

			// 协议绑定确认，会重置订单状态，所以可能存在订单是初始化状态
			if (FuiouDepositStateEnum.INI.isValue(fuiouUserProtocol.getState())) {
				// 如果订单是初始化状态，则返回需要先获取短信的异常
				logger.info(Log.op(op).msg("富友存管协议绑定确认，更新订单状态为处理中，没有命中，且订单状态为初始化状态").kv("busiId", fuiouUserProtocol.getBusiId()).toString());
				return PayRspCodeVO.newInstance(PayExceptionCode.ERROR_BEFORE_SMS);
			}

			// 如果不是初始化状态，则返回订单结果
			return PayRspCodeVO.newInstance(po.getRspCode(), po.getRspMsg());
		}

		// 请求富友存管协议绑定确认接口
		PayRspCodeVO rspCodeVO = fuiouDepositService.doProtocolSigning(fuiouUserProtocol, salt, validCode);

		FuiouDepositStateEnum state = FuiouDepositStateEnum.FAIL;
		if (PayExceptionCode.SUCCESS.isCode(rspCodeVO.getCode())) {
			// 如果请求成功，则更新订单状态为成功
			state = FuiouDepositStateEnum.SUCCESS;
		} else if (PayExceptionCode.isToConfirm(rspCodeVO.getCode())) {
			// 如果请求是待确认状态，则订单状态为待确认
			state = FuiouDepositStateEnum.ACCEPTED;
		} else if (PayExceptionCode.isTimeout(rspCodeVO.getCode())) {
			state = FuiouDepositStateEnum.TIMEOUT;
		} else if (PayExceptionCode.VERIFY_CODE_INVALID.isCode(rspCodeVO.getCode())) {
			// 如果请求是验证码输错，订单支持验证码输错3次，如果还没超过3次，则重置订单状态
			String timesStr = cacheService.get(getProtocolSigningMaxErrorTimesKey(fuiouUserProtocol));
			Integer times = StringUtils.isBlank(timesStr) ? 0 : Integer.valueOf(timesStr);
			if (times != null && times <= protocolSignSmsMaxTimes) {
				state = FuiouDepositStateEnum.INI;
			}
		}

		// 如果订单状态不是初始化状态，则清除token,防止无效请求
		if (!FuiouDepositStateEnum.INI.isValue(state.getValue())) {
			removeProtocolSignSmsToken(fuiouUserProtocol);
		}

		fuiouUserProtocolDao.updateState(fuiouUserProtocol.getBusiId(), state.getValue(), FuiouDepositStateEnum.DEALING.getValue(),
				rspCodeVO.getCode(), rspCodeVO.getDesc());

		return rspCodeVO;
	}

	/**
	 * 删除协议绑定短信token
	 * 
	 * @author buyi
	 * @date 2018-06-26 18:00:01
	 * @since v2.2.2
	 * @param fuiouUserProtocol
	 */
	private void removeProtocolSignSmsToken(FuiouUserProtocol fuiouUserProtocol) {
		cacheService.delete(getProtocolSignSmsTokenKey(fuiouUserProtocol));

	}

	@Override
	public OpResponse queryProtocolSignComposite(String merchantCode, QueryUserProtocolSignReq params) {
		OpResponse opResponse = new OpResponse();
		try {

			PayRspCodeVO payRspCodeVO = fuiouDepositService.queryProtocolSign(merchantCode, params);

			opResponse.setCode(payRspCodeVO.getCode());
			opResponse.setDisplay(payRspCodeVO.getDesc());
			opResponse.setContent(payRspCodeVO.getObj());

			return opResponse;

		} catch (Exception e) {

		}
		return opResponse;
	}
}
